package Control;

import GameControl.GameControl;
import Main.Main;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

/**
 * Class <code>SwimLines</code> implements version processing system storage.
 * @author Dimon
 */
public class SwimLines implements Serializable {

    /**
     * Stores max version numbers for each participant.<p>
     * Also it stores participants list as dictionary keys.
     */
    private HashMap<String, Integer> maximums;
    /**
     * Collects versions before they are about collapsing.<p>
     * <code>storage</code> is a sorted set, so it collapses optimally.
     */
    private TreeSet<Version> storage;
    /**
     * Base Version Number is a number of last collapsed version.
     */
    private int BVN;
    /**
     * Maximum of version numbers.
     */
    private int MAX;

    /**
     * Creates new pure SwimLine.
     */
    public SwimLines() {
        maximums = new HashMap();
        storage = new TreeSet();
        BVN = 0;
        MAX = 0;
    }

    /**
     * Adds the new swimline for given participant.
     * @param ID participant ID to be added
     */
    public void addParticipant(String ID) {
        if (!maximums.containsKey(ID)) {
            maximums.put(ID, 0);
        }
    }

    /**
     * Adds given version to the storage. Maintains state of the storage.
     * Collapses.
     * @param version version to be added
     */
    public void addVersion(Version version) {
        //Validate
        if (version.getNumber() <= BVN) {
            Warn("such versions have been collapsed already: " + version.toString());
            return;
        }
        if (!participates(version.getID())) {
            Warn("sender doesn't participate: " + version.toString());
            return;
        }
        //Update
        storage.add(version);
        if (version.getNumber() > MAX) {
            MAX = version.getNumber();
        }
        if (version.getNumber() > maximums.get(version.getID())) {
            maximums.put(version.getID(), version.getNumber());
        }
        //Remove
        if (storage.size() > maximums.size()) {
            Debug("non-respondings detected");
            for (Iterator<String> i = ((HashMap) maximums.clone()).keySet().iterator(); i.hasNext();) {
                String s = i.next();
                if (maximums.get(s) <= BVN) {
                    maximums.remove(s);
                    Debug("removed " + s);
                }
            }
        }
        //What to collapse
        int min = MAX;
        for (Iterator<Integer> i = maximums.values().iterator(); i.hasNext();) {
            int n = i.next();
            if (n < min) {
                min = n;
            }
        }
        Debug("collapse may happen between " + BVN + " and " + min);
        //Collapse
        int result = 0;
        boolean success = false;
        for (Iterator<Version> i = ((TreeSet<Version>) storage.clone()).iterator();
                i.hasNext();) {
            Version v = i.next();
            if (v.getNumber() <= min) {
                success = true;
                result = (result + v.getCode()) % 4;
                storage.remove(v);
            }
        }
        //Code
        if (success) {
            BVN = min;
            Debug("collapsed to " + result + " code");
            GameControl.GameCommand(result);
        } else {
            Debug("no collapse");
        }
    }

    /**
     * Returns current leader (first) participant.
     * @return leader participant ID
     */
    public String getLeader() {
        return ((Iterator<String>) ((Set) maximums.keySet()).iterator()).next();
    }

    /**
     * Returns either given participant exists or not.
     * @param ID participant ID to be checked
     * @return existance of participant
     */
    public boolean participates(String ID) {
        return maximums.containsKey(ID);
    }

    /**
     * Generates valid version number.
     * @return new version number
     */
    public int nextNumber() {
        MAX++;
        return MAX;
    }

    /**
     * Writes debug messages via <code>Main.PrintDebug</code>.
     * @param txt text to be written
     */
    private void Debug(String txt) {
        Main.PrintDebug(3, "SwimLines: " + txt);
    }

    /**
     * Warns via <code>Main.PrintDebug</code>.
     * @param txt text to be written
     */
    private void Warn(String txt) {
        Main.PrintDebug(4, "SwimLines: " + txt);
    }
}
